package com.fly.platform.common.aop

import com.fly.platform.base.pojo.elasticsearch.EsLog
import com.fly.platform.base.service.elasticsearch.EsLogService
import com.fly.platform.common.annotation.SystemLog
import com.fly.platform.common.utils.ThreadPoolUtil
import org.aspectj.lang.JoinPoint
import org.aspectj.lang.annotation.AfterReturning
import org.aspectj.lang.annotation.Aspect
import org.aspectj.lang.annotation.Before
import org.aspectj.lang.annotation.Pointcut
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.core.NamedThreadLocal
import org.springframework.security.core.context.SecurityContextHolder
import org.springframework.security.core.userdetails.UserDetails
import org.springframework.stereotype.Component
import java.util.*
import javax.servlet.http.HttpServletRequest

/**
 * Spring AOP实现日志管理
 * @author fly
 */
@Aspect
@Component
class SystemLogAspect {

    @Autowired(required = false)
    private lateinit var request: HttpServletRequest

    @Autowired
    private lateinit var esLogService: EsLogService


    /**
     * Controller层切点,注解方式
     */
    @Pointcut("@annotation(com.fly.platform.common.annotation.SystemLog)")
    fun controllerAspect() {}

    /**
     * 前置通知 (在方法执行之前返回)用于拦截Controller层记录用户的操作的开始时间
     * @param joinPoint 切点
     * @throws InterruptedException
     */
    @Before("controllerAspect()")
    @Throws(InterruptedException::class)
    fun doBefore(joinPoint: JoinPoint?) {

        //线程绑定变量（该数据只有当前请求的线程可见）
        val beginTime = Date()
        beginTimeThreadLocal.set(beginTime)
    }

    /**
     * 后置通知(在方法执行之后并返回数据) 用于拦截Controller层无异常的操作
     * @param joinPoint 切点
     */
    @AfterReturning("controllerAspect()")
    @Throws(Exception::class)
    fun after(joinPoint: JoinPoint) {

        //调用线程保存至ES
        ThreadPoolUtil.getPool().execute {

            esLogService.saveLog(EsLog().apply {

                username = SecurityContextHolder.getContext().authentication.principal?.run {

                    if ("anonymousUser" != this.toString()) {

                        val user = this as UserDetails
                        user.username
                    } else "未知"
                } ?: "未知"

                name = getControllerMethodInfo(joinPoint)["description"].toString()
                requestUrl = request.requestURI
                requestType = request.method
                costTime = (System.currentTimeMillis() - beginTimeThreadLocal.get().time).toInt()
            })
        }
    }


    companion object {

        private val beginTimeThreadLocal: ThreadLocal<Date> = NamedThreadLocal("ThreadLocal beginTime")

        /**
         * 获取注解中对方法的描述信息 用于Controller层注解
         * @param joinPoint 切点
         * @return 方法描述
         * @throws Exception
         */
        @Throws(Exception::class)
        fun getControllerMethodInfo(joinPoint: JoinPoint): Map<String, Any?> {

            val map: MutableMap<String, Any?> = HashMap(16)
            //获取目标类名
            val targetName = joinPoint.target.javaClass.name
            //获取方法名
            val methodName = joinPoint.signature.name
            //获取相关参数
            val arguments = joinPoint.args
            //生成类对象
            val targetClass = Class.forName(targetName)
            //获取该类中的方法
            val methods = targetClass.methods
            var description: String
            for (method in methods) {
                if (method.name != methodName) {
                    continue
                }
                val clazzs = method.parameterTypes
                if (clazzs.size != arguments.size) {
                    //比较方法中参数个数与从切点中获取的参数个数是否相同，原因是方法可以重载哦
                    continue
                }
                description = method.getAnnotation(SystemLog::class.java).description
                map["description"] = description
            }
            return map
        }
    }
}